package communication;

import data.Logger;
import environment.*;
import java.util.*;

import communication.Message.MessageType;

/**
 * Manager class to handle sending/broadcasting of messages
 * @author Toar
 *
 */
public class MessageManager 
{
	/** This is the chance that a message is successfully sent **/
	/** used to simulate real TCP/IP communication and packet failure **/
	public static final double MESSAGE_SUCCESS_RATE = 1; //0.99;
	/** This array of all objects must be set initially from the Main program **/
	public static ArrayList<EnvObjInterface> a;
	
	/** COUNTERS **/
	public static int COST_MESSAGE_SENT = 0;
	public static int COST_MESSAGE_RCVD = 0;
	
	public static int WINNER_MESSAGE_SENT = 0;
	public static int WINNER_MESSAGE_RCVD = 0;
	
	public static int HELP_MESSAGE_SENT = 0;
	public static int HELP_MESSAGE_RCVD = 0;
	
	public static int NOTIFY_MESSAGE_SENT = 0;
	public static int NOTIFY_MESSAGE_RCVD = 0;
	
	public static int IGNORE_MESSAGE_SENT = 0;
	public static int IGNORE_MESSAGE_RCVD = 0;
	
	public static int ARRIVE_MESSAGE_SENT = 0;
	public static int ARRIVE_MESSAGE_RCVD = 0;
	
	public static int FP_MESSAGE_SENT = 0;
	public static int FP_MESSAGE_RCVD = 0;
	
	public static int ARE_YOU_ALIVE_B4_SENT = 0;
	public static int ARE_YOU_ALIVE_B4_RCVD = 0;	

	public static int I_AM_ALIVE_B4_SENT = 0;
	public static int I_AM_ALIVE_B4_RCVD = 0;
	
	public static int I_AM_DYING_B4_SENT = 0;
	public static int I_AM_DYING_B4_RCVD = 0;

	public static int ARE_YOU_ALIVE_TX_SENT = 0;
	public static int ARE_YOU_ALIVE_TX_RCVD = 0;	

	public static int I_AM_ALIVE_TX_SENT = 0;
	public static int I_AM_ALIVE_TX_RCVD = 0;

	public static int I_AM_DYING_TX_SENT = 0;
	public static int I_AM_DYING_TX_RCVD = 0;

	
	/** 
	 * NOTE: ARE_YOU_ALIVE and I_AM_ALIVE messages during and before transportation
	 *       needs to be separated.
	 */
	public static int TOTAL_COORDINATION_BEFORE_TX = 0;
	public static int TOTAL_COORDINATION_DURING_TX = 0;
	
	/**
	 * Broadcasts cost message to all other robots
	 * @param r
	 * @param b
	 * @param a
	 */
	public static void sendCostMessage(String senderID, String coordID, String boxID, int cost)
	{
		Message costMessage = new Message( boxID, senderID, coordID, MessageType.COST);
		costMessage.setCost(cost);
		for(EnvObjInterface e : a)
		{
			if(e.getID().startsWith("r") && !e.getID().equals(senderID) && !e.getID().equals(coordID))
			{
				Robot r2 = (Robot) e;
				
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + r2.getID() + ": "+ costMessage);
				COST_MESSAGE_SENT++;
				
				receive(r2, costMessage);
			}
		}
	}
	
	/**
	 * Broadcasts help message to all other robots
	 * @param r
	 * @param b
	 * @param a
	 */
	public static void sendHelpMessage(Robot r, Box b)
	{
		if(Main.NO_ARTIFICIAL_IMMUNE) return;
		Message helpMessage = new Message( b.getID(), r.getID(), r.getID(), MessageType.HELP);
		/** checks for dominator (other than the sending robot) **/
		int success = Main.LEARNING_RATE;
		boolean dominatorExists = Whiteboard.existsDominator(r.getID(), b.getID(), success);
		
		for(EnvObjInterface e : a)
		{
			if(e.getID().startsWith("r") && !e.getID().equals(r.getID()))
			{
				Robot r2 = (Robot) e;
				
				/** check for success rate learning **/
				if(Main.SUCCESS_RATE_LEARNING)
				{
					/** check for success rate **/
					if(dominatorExists)
					{
						if(Whiteboard.isDominator(r2.getID(), b.getID(), success))
						{
							Logger.log("SEND " + r.getID() + " -> " + r2.getID() + ": "+ helpMessage);
							HELP_MESSAGE_SENT++;
							receive(r2, helpMessage);
						}
						else
						{
							Logger.log("DONT_SEND " + r.getID() + " X NOT_DOMINATOR: " + r2.getID());
						}
					}
					else
					{
						Logger.log("SEND " + r.getID() + " -> " + r2.getID() + ": "+ helpMessage);
						HELP_MESSAGE_SENT++;
						receive(r2, helpMessage);
					}
				}
				else
				{
					Logger.log("SEND " + r.getID() + " -> " + r2.getID() + ": "+ helpMessage);
					HELP_MESSAGE_SENT++;
					receive(r2, helpMessage);
				}
			}
		}
	}
	
	/**
	 * Broadcasts box location and capabilities to other robots
	 * No coordinatorID! This is usually when a robot finds a box, and cannot push it
	 * @param r
	 * @param b
	 * @param a
	 */
	public static void sendBoxNotifyMessage(String senderID, Box b)
	{
		if(Main.NO_ARTIFICIAL_IMMUNE) return;
		Message boxNotifyMessage = new Message( b.getID(), senderID, "", MessageType.BOX_NOTIFY);
		/** checks for dominator (other than the sending robot) **/
		int success = Main.LEARNING_RATE;
		boolean dominatorExists = Whiteboard.existsDominator(senderID, b.getID(), success);
		
		for(EnvObjInterface e : a)
		{
			if(e.getID().startsWith("r") && !e.getID().equals(senderID))
			{
				Robot r2 = (Robot) e;
				
				/** check for success rate learning **/
				if(Main.SUCCESS_RATE_LEARNING)
				{
					/** check for success rate **/
					if(dominatorExists)
					{
						if(Whiteboard.isDominator(r2.getID(), b.getID(), success))
						{
							Logger.log("SEND " + senderID + " -> " + r2.getID() + ": "+ boxNotifyMessage);
							NOTIFY_MESSAGE_SENT++;
							receive(r2, boxNotifyMessage);
						}
						else
						{
							Logger.log("DONT_SEND " + senderID + " X NOT_DOMINATOR: " + r2.getID());
						}
					}
					else
					{
						Logger.log("SEND " + senderID + " -> " + r2.getID() + ": "+ boxNotifyMessage);
						NOTIFY_MESSAGE_SENT++;
						receive(r2, boxNotifyMessage);
					}
				}
				else
				{
					Logger.log("SEND " + senderID + " -> " + r2.getID() + ": "+ boxNotifyMessage);
					NOTIFY_MESSAGE_SENT++;
					receive(r2, boxNotifyMessage);
				}
			}
		}
	}
	
	/**
	 * Sends a message to ignore a box
	 * Usually when receiving redundant ARRIVE_TO_HELP message
	 * @param r
	 * @param b
	 * @param a
	 */
	public static void sendBoxIgnoreMessage(String senderID, String receiverID, String boxID)
	{
		Message boxIgnoreMessage = new Message( boxID, senderID, "", MessageType.BOX_IGNORE);
		for(EnvObjInterface e : a)
		{
			if(e.getID().startsWith("r") && e.getID().equals(receiverID))
			{
				Robot r2 = (Robot) e;
				
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + receiverID + ": "+ boxIgnoreMessage);
				IGNORE_MESSAGE_SENT++;
				
				receive(r2, boxIgnoreMessage);
				return;
			}
		}
	}
	
	/**
	 * @deprecated
	 * @param senderID
	 * @param coordID
	 * @param boxID
	 */
	public static void sendRespondHelpMessage(String senderID, String coordID, String boxID)
	{
		Message respondMessage = new Message( boxID, senderID, coordID, MessageType.RESPOND_TO_HELP);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(coordID))
			{
				Robot coordRobot = (Robot) e;
				receive(coordRobot, respondMessage);
			}
		}
	}
	
	public static void sendArriveToHelpMessage(String senderID, String coordID, String boxID)
	{
		Message arriveMessage = new Message( boxID, senderID, coordID, MessageType.ARRIVE_TO_HELP);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(coordID))
			{
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + coordID + ": "+ arriveMessage);
				ARRIVE_MESSAGE_SENT++;
				
				Robot coordRobot = (Robot) e;
				receive(coordRobot, arriveMessage);
			}
		}
	}
	
	public static void sendGoToForcePointMessage(String coordID, String allyID, String boxID, int fp)
	{
		Message arriveMessage = new Message( boxID, coordID, coordID, MessageType.GO_TO_FORCE_POINT, fp);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(allyID))
			{
				Robot coordRobot = (Robot) e;
				FP_MESSAGE_SENT++;
				receive(coordRobot, arriveMessage);
			}
		}
	}
	
	public static void sendWinnerMessage(String senderID, String coordID, String boxID, double dist)
	{
		Message winnerMessage = new Message( boxID, senderID, coordID, MessageType.I_AM_THE_WINNER);
		winnerMessage.setDist(dist);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(coordID))
			{
				Logger.log("SEND " + senderID + " -> " + coordID + ": "+ winnerMessage);
				Robot coordRobot = (Robot) e;
				WINNER_MESSAGE_SENT++;
				receive(coordRobot, winnerMessage);
			}
		}
	}
	
	public static void sendAreYouAliveMessage(String senderID, String allyID, String boxID, boolean beforeTransportation)
	{
		Message areYouAlive = new Message( boxID, senderID, allyID, MessageType.ARE_YOU_ALIVE);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(allyID))
			{
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + allyID + ": "+ areYouAlive);
				if(beforeTransportation) 
					ARE_YOU_ALIVE_B4_SENT++;
				else 
					ARE_YOU_ALIVE_TX_SENT++;
					
				Robot allyRobot = (Robot) e;
				receive(allyRobot, areYouAlive);
			}
		}
	}
	
	public static void sendImAlive(String senderID, String coordID, String boxID, boolean beforeTransportation)
	{
		Message ImAlive = new Message( boxID, senderID, coordID, MessageType.I_AM_ALIVE);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(coordID))
			{
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + coordID + ": "+ ImAlive);
				if(beforeTransportation) 
					I_AM_ALIVE_B4_SENT++;
				else 
					I_AM_ALIVE_TX_SENT++;
				
				Robot allyRobot = (Robot) e;
				receive(allyRobot, ImAlive);
			}
		}
	}

	public static void sendImDying(String senderID, String receiverID, String boxID, boolean beforeTransportation)
	{
		Message dyingMsg = new Message( boxID, senderID, receiverID, MessageType.I_AM_DYING);
		for(EnvObjInterface e : a)
		{
			if(e.getID().equals(receiverID))
			{
				/** Log and increment count **/
				Logger.log("SEND " + senderID + " -> " + receiverID + ": "+ dyingMsg);
				if(beforeTransportation) 
					I_AM_DYING_B4_SENT++;
				else 
					I_AM_DYING_TX_SENT++;
				
				Robot allyRobot = (Robot) e;
				receive(allyRobot, dyingMsg);
			}
		}
	}
	
	private static void receive(Robot r, Message m)
	{
		double d = Math.random(); 
		if(d <= MESSAGE_SUCCESS_RATE)
			if(m.getMessageType() == Message.MessageType.ARE_YOU_ALIVE) r.receiveAliveMessage(m);
			else
				r.receiveMessage(m);
		else
			System.err.println("NOT SENT: " + d + " " + m);
	}

	public static int getTotalCoordinationMsgBeforeTx()
	{
		return HELP_MESSAGE_SENT + WINNER_MESSAGE_SENT + 
			IGNORE_MESSAGE_SENT + NOTIFY_MESSAGE_SENT + 
			ARRIVE_MESSAGE_SENT + COST_MESSAGE_SENT +
			ARE_YOU_ALIVE_B4_SENT + I_AM_ALIVE_B4_SENT + I_AM_DYING_B4_SENT;
	}
	
	public static int getTotalCoordinationMsgDuringTx()
	{
		return FP_MESSAGE_SENT + ARE_YOU_ALIVE_TX_SENT + I_AM_ALIVE_TX_SENT + I_AM_DYING_TX_SENT;
	}
	
	public static void resetCount()
	{
		MessageManager.HELP_MESSAGE_SENT = 0;
		MessageManager.IGNORE_MESSAGE_SENT = 0;
		MessageManager.NOTIFY_MESSAGE_SENT = 0;
		MessageManager.ARRIVE_MESSAGE_SENT = 0;
		MessageManager.COST_MESSAGE_SENT = 0;
		MessageManager.FP_MESSAGE_SENT = 0;
		MessageManager.WINNER_MESSAGE_SENT = 0;
		
		MessageManager.I_AM_DYING_B4_SENT = 0;
		MessageManager.I_AM_ALIVE_B4_SENT = 0;
		MessageManager.ARE_YOU_ALIVE_B4_SENT = 0;
		
		MessageManager.I_AM_DYING_TX_SENT = 0;
		MessageManager.I_AM_ALIVE_TX_SENT = 0;
		MessageManager.ARE_YOU_ALIVE_TX_SENT = 0;
	}
}
